﻿//
// PluginServices.cs: Imports plugins into S9Pal.
//
// NOTE: This code was derived from code originally written by Redth.
// The source of which can be found at
// <http://www.codeproject.com/KB/cs/pluginsincsharp.aspx>.
// 
// Copyright 2010 Michael Goethe
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

using System;
using System.IO;
using System.Reflection;
using S9PalCommon;

namespace S9Pal
{
    /// <summary>
    /// Imports plugins into S9Pal.
    /// </summary>
    public class PluginServices
    {
        #region Fields

        private AvailablePlugins availablePlugins = new AvailablePlugins();

        #endregion

        #region Public Methods

        /// <summary>
        /// Collection of available plugins as loaded by FindPlugins().
        /// </summary>
        public AvailablePlugins AvailablePlugins
        {
            get { return availablePlugins; }
            set { availablePlugins = value; }
        }

        /// <summary>
        /// Search for plugins within the plugins directory.
        /// </summary>
        public void FindPlugins()
        {
            FindPlugins(S9PalCommon.Global.PluginsDirectoryPath);
        }

        /// <summary>
        /// Search for plugins at the specified path.
        /// </summary>
        /// <param name="Path">Directory to search.</param>
        public void FindPlugins(String path)
        {
            bool retry = false;

            // Clear the available plugins.
            availablePlugins.Clear();

            do
            {
                try
                {
                    // Search all files in the specified directory.
                    foreach (String fileName in Directory.GetFiles(path))
                    {
                        FileInfo file = new FileInfo(fileName);

                        // If the file is a DLL, add the plugin. The AddPlugin method will determine whether the file is a legitimate plugin. 
                        if (file.Extension.Equals(".dll"))
                            this.AddPlugin(fileName);
                    }
                    retry = false;
                }
                catch (DirectoryNotFoundException)
                {
                    Directory.CreateDirectory(path);
                    retry = true;
                }
            } while (retry);
        }

        /// <summary>
        /// Closes all available plugins.
        /// </summary>
        public void ClosePlugins()
        {
            foreach (AvailablePlugin plugin in availablePlugins)
            {
                // Allow the plugin to run any disposal code that it may have and then null it out.
                plugin.Instance.Dispose();
                plugin.Instance = null;
            }

            // Clear the availablePlugins collection.
            availablePlugins.Clear();
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Attempts to add a plugin given its file name.
        /// </summary>
        /// <param name="fileName">Plugin's file name.</param>
        private void AddPlugin(String fileName)
        {
            // Create a new assembly from the plugin file.
            Assembly pluginAssembly = Assembly.LoadFrom(fileName);

            // Loop through all of the types found in the assembly.
            foreach (Type pluginType in pluginAssembly.GetTypes())
            {
                if (pluginType.IsPublic) // Only look at public types.
                {
                    if (!pluginType.IsAbstract)  // Only look at non-abstract types.
                    {
                        // Get a type object of the matching interface.
                        Type typeInterface = pluginType.GetInterface("S9PalCommon.IPlugin", true);

                        // If not null, then the type implements our interface.
                        if (typeInterface != null)
                        {
                            AvailablePlugin newPlugin = new AvailablePlugin();

                            // Create a new instance of our plugin.
                            newPlugin.Instance = (IPlugin)Activator.CreateInstance(pluginAssembly.GetType(pluginType.ToString()));

                            // Run the intialization code implemented by the plugin.
                            newPlugin.Instance.Initialize();

                            // Add the new plugin to the collection.
                            this.availablePlugins.Add(newPlugin);

                            newPlugin = null;
                        }
                        typeInterface = null;			
                    }
                }
            }
            pluginAssembly = null;
        }

        #endregion

    }

    /// <summary>
    /// Collection for AvailablePlugin type.
    /// </summary>
    public class AvailablePlugins : System.Collections.CollectionBase
    {
        #region Public Methods

        /// <summary>
        /// Add a plugin to the collection.
        /// </summary>
        /// <param name="pluginToAdd">Plugin to add.</param>
        public void Add(AvailablePlugin pluginToAdd)
        {
            this.List.Add(pluginToAdd);
        }

        /// <summary>
        /// Remove a plugin from the collection.
        /// </summary>
        /// <param name="pluginToRemove">Plugin to remove.</param>
        public void Remove(AvailablePlugin pluginToRemove)
        {
            this.List.Remove(pluginToRemove);
        }

        /// <summary>
        /// Find a plugin in the collection.
        /// </summary>
        /// <param name="pluginNameOrPath">The name or file path of the plugin.</param>
        /// <returns>Matching plugin; null if not found.</returns>
        public AvailablePlugin Find(String pluginNameOrPath)
        {
            AvailablePlugin matchingPlugin = null;

            foreach (AvailablePlugin plugin in this.List)
            {
                // Find plugin with matching name or assembly path.
                if ((plugin.Instance.Name.Equals(pluginNameOrPath)))
                {
                    matchingPlugin = plugin;
                    break;
                }
            }
            return matchingPlugin;
        }

        #endregion
    }

    /// <summary>
    /// Contains an instance for each available plugin.
    /// </summary>
    public class AvailablePlugin
    {
        #region Fields

        private IPlugin instance = null;

        #endregion

        #region Properties

        /// <summary>
        /// Instance of the plugin.
        /// </summary>
        public IPlugin Instance
        {
            get 
            { 
                return instance; 
            }
            set 
            { 
                instance = value; 
            }
        }

        #endregion
    }
}
